﻿///////////////////////////////////////////////////////////////////////////////
//
// This file is part of the BassDrop project
//   https://code.google.com/p/bass-drop/
//
//
// Creator:	Gergely Krajcsovszki (krajcsovszkig@hotmail.com)
// Date:	2014-03-08 11:30
//
//
// Copyright © 2013-2014. Gergely Krajcsovszki
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BassDrop.Core.Common.Exceptions;
using BassDrop.Core.Common.Localization;

namespace BassDrop.Core.Common.Cloud.Assets
{
	/// <summary>
	/// Represents a vector clock for timing messages accross the cloud
	/// </summary>
	public class VectorClock : IEnumerable<VectorClock.VectorClockElement>, IComparable<VectorClock>, IEquatable<VectorClock>
	{
		private List<VectorClockElement> m_vector = new List<VectorClockElement>();
		private VectorClockElement m_myVectorClockElement;

		/// <summary>
		/// Constructs a new vector clock
		/// </summary>
		/// <param name="myClientID">the ID of this client</param>
		public VectorClock( string myClientID )
		{
			m_myVectorClockElement = new VectorClockElement( myClientID );
			m_vector.Add( m_myVectorClockElement );
		}

		/// <summary>
		/// Copy constructor
		/// </summary>
		/// <param name="copyFrom"></param>
		protected VectorClock( VectorClock copyFrom )
		{
			foreach ( var item in copyFrom.m_vector )
			{
				m_vector.Add( item.Clone() );
			}

			m_myVectorClockElement = Find( copyFrom.m_myVectorClockElement.ClientID );
		}

		/// <summary>
		/// Updates the local vector using the passed one
		/// </summary>
		/// <param name="vectorClock"></param>
		public void Update( VectorClock vectorClock )
		{
			foreach ( var item in vectorClock )
			{
				AddOrUpdate( item );
			}
		}

		/// <summary>
		/// Increment the vector clock element of this client
		/// </summary>
		public void Increment()
		{
			m_myVectorClockElement.Increment();
		}

		/// <summary>
		/// Finds the vector clock element of the client with the passed ID
		/// </summary>
		/// <param name="clientID"></param>
		/// <returns></returns>
		public VectorClockElement Find( string clientID )
		{
			return (from item
					in m_vector
					where item.ClientID == clientID
					select item)
						.FirstOrDefault();
		}

		/// <summary>
		/// Tries to find an element in the vector with the same clientID as the passed one and update it;
		/// if none fount, adds the element to the vector.
		/// </summary>
		/// <param name="element"></param>
		private void AddOrUpdate( VectorClockElement element )
		{
			foreach ( var item in m_vector )
			{
				if ( item.ClientID == element.ClientID )
				{
					item.Update( element.Counter, element.LastUpdated );
					return;
				}
			}

			m_vector.Add( element );
		}

		/// <summary>
		/// Parses the vector clock from the contents of a vector clock file passed as string
		/// </summary>
		/// <param name="text">the contents of a vector clock file in a string</param>
		/// <returns>the VectorClock parsed from the text</returns>
		public static VectorClock Parse( string text )
		{
			try
			{
				string[] tokens = text.Split( '\t' );

				VectorClock clock = new VectorClock( tokens[0] );

				for ( int i = 1; i < tokens.Length; i += 3 )
				{
					clock.AddOrUpdate( new VectorClockElement( tokens[i], long.Parse( tokens[i + 1] ), DateTime.Parse( tokens[i + 2] ) ) );
				}

				return clock;
			}
			catch ( Exception e )
			{
				throw new DataFormatException( Strings.ExceptionBadVectorClockFile, e );
			}
		}

		/// <summary>
		/// Converts this vector clock to text that can be saved in a file and later parsed
		/// </summary>
		/// <returns></returns>
		public string ToVectorClockText()
		{
			StringBuilder builder = new StringBuilder();

			builder.Append( m_myVectorClockElement.ClientID );

			foreach ( var item in m_vector )
			{
				builder.Append( '\t' );
				builder.Append( item.ClientID );
				builder.Append( '\t' );
				builder.Append( item.Counter );
				builder.Append( '\t' );
				builder.Append( item.LastUpdated.ToString( "o" ) );
			}

			return builder.ToString();
		}

		/// <summary>
		/// Increments the clock and returns a copy of it
		/// </summary>
		/// <returns></returns>
		public VectorClock GetCopy()
		{
			Increment();

			return new VectorClock( this );
		}

		#region IEnumerable<VectorClockElement> Members

		/// <summary>
		/// Gets the enumerator of the vector
		/// </summary>
		/// <returns></returns>
		public IEnumerator<VectorClock.VectorClockElement> GetEnumerator()
		{
			return m_vector.GetEnumerator();
		}

		#endregion

		#region IEnumerable Members

		/// <summary>
		/// Gets the enumerator of the vector
		/// </summary>
		/// <returns></returns>
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return m_vector.GetEnumerator();
		}

		#endregion

		#region IComparable<VectorClock> Members

		/// <summary>
		/// Compares the current instance with another object of the same type and returns an integer 
		/// that indicates whether the current instance precedes, follows, or occurs in the same position 
		/// in the sort order as the other object.
		/// </summary>
		/// <param name="other">An object to compare with this instance.</param>
		/// <returns>
		///		A value that indicates the relative order of the objects being compared. The return value has these meanings:
		///		<list type="table">
		///			<listheader><item>Value</item><item>Meaning</item></listheader>		
		///			<item><item>Less than zero</item><item>This instance precedes obj in the sort order.</item></item>
		///			<item><item>Zero</item><item>This instance occurs in the same position in the sort order as obj.</item></item>
		///			<item><item>Greater than zero</item><item>This instance follows obj in the sort order.</item></item>
		///		</list>
		/// </returns>
		public int CompareTo( VectorClock other )
		{
			int retval = 0;

			foreach ( var item in m_vector )
			{
				VectorClockElement otherElement = other.Find( item.ClientID );

				if ( otherElement != null )
				{
					if ( item.Counter < otherElement.Counter && (retval == 0 || retval == -1) )
					{
						retval = -1;
					}
					else if ( item.Counter > otherElement.Counter && (retval == 0 || retval == +1) )
					{
						retval = +1;
					}
					else if ( item.Counter != otherElement.Counter )
					{
						throw new InvalidOperationException( "The two vector clocks cannot be compared because they have conflicting differences." );
					}
				}
			}

			return retval;
		}

		#endregion

		#region IEquatable<VectorClock> Members

		/// <summary>
		/// Indicates whether the current object is equal to another object of the same type.
		/// </summary>
		/// <param name="other">An object to compare with this object.</param>
		/// <returns>true if the current object is equal to the other parameter; otherwise, false.</returns>
		public bool Equals( VectorClock other )
		{
			if ( (object) other == null )
			{
				return false;
			}

			foreach ( var item in m_vector )
			{
				VectorClockElement otherElement = other.Find( item.ClientID );

				if ( otherElement != null && item.Counter != otherElement.Counter )
				{
					return false;
				}
			}

			return true;
		}

		#endregion

		/// <summary>
		/// Returns whether v1 is less than v2
		/// </summary>
		/// <param name="v1"></param>
		/// <param name="v2"></param>
		/// <returns></returns>
		public static bool operator <( VectorClock v1, VectorClock v2 )
		{
			return v1.CompareTo( v2 ) < 0;
		}

		/// <summary>
		/// Returns whether v1 is greater than v2
		/// </summary>
		/// <param name="v1"></param>
		/// <param name="v2"></param>
		/// <returns></returns>
		public static bool operator >( VectorClock v1, VectorClock v2 )
		{
			return v1.CompareTo( v2 ) > 0;
		}

		/// <summary>
		/// Determines whether the specified object is equal to the current object.
		/// </summary>
		/// <param name="obj">An object to compare with this object.</param>
		/// <returns>true if the current object is equal to the other parameter; otherwise, false.</returns>
		public override bool Equals( object obj )
		{
			return Equals( obj as VectorClock );
		}

		/// <summary>
		/// Serves as the default hash function.
		/// </summary>
		/// <returns>A hash code for the current object.</returns>
		public override int GetHashCode()
		{
			return m_vector.GetHashCode();
		}

		/// <summary>
		/// Returns whether v1 equals v2
		/// </summary>
		/// <param name="v1"></param>
		/// <param name="v2"></param>
		/// <returns></returns>
		public static bool operator ==( VectorClock v1, VectorClock v2 )
		{
			if ( System.Object.ReferenceEquals( v1, v2 ) )
			{
				return true;
			}

			if ( ((object) v1 == null) || ((object) v2 == null) )
			{
				return false;
			}

			return v1.Equals( v2 );
		}

		/// <summary>
		/// Returns whether v1 does not equal v2
		/// </summary>
		/// <param name="v1"></param>
		/// <param name="v2"></param>
		/// <returns></returns>
		public static bool operator !=( VectorClock v1, VectorClock v2 )
		{
			return !(v1 == v2);
		}

		#region Inner classes

		/// <summary>
		/// An element of the vector clock, contains the ClientID of the client it belongs to,
		/// the state of the counter the UTC timestamp of its last update in the time of that client
		/// and the UTC timestamp for when the update was received in the time of this client.
		/// </summary>
		public class VectorClockElement
		{
			/// <summary>
			/// The ClientID of the client this element belongs to
			/// </summary>
			public string ClientID
			{
				get;
				private set;
			}

			/// <summary>
			/// The state of the counter / timer
			/// </summary>
			public long Counter
			{
				get;
				private set;
			}

			/// <summary>
			/// The UTC timestamp of the last update of the counter in the time of the client it belongs to
			/// </summary>
			public DateTime LastUpdated
			{
				get;
				private set;
			}

			/// <summary>
			/// The UTC timestamp for when the last update was received in the time of this client
			/// </summary>
			public DateTime LastReceived
			{
				get;
				private set;
			}

			/// <summary>
			/// Returns the estimated current UTC timestamp in the time of the client this element belongs to.
			/// Guaranteed to be less than or equal to the actual time of the remote client.
			/// </summary>
			public DateTime CurrentTimeEstimate // TODO: pontosabb becslés valahogy. Most gyakorlatilag az utolsó update óta eltelt időt méri
			{
				get
				{
					return DateTime.UtcNow - (LastReceived - LastUpdated);
				}
			}

			/// <summary>
			/// Constructs a new vector clock element with 0 counter
			/// </summary>
			/// <param name="clientID"></param>
			public VectorClockElement( string clientID )
				: this( clientID, 0, DateTime.UtcNow )
			{
			}

			/// <summary>
			/// Constructs a new vector clock element with the passed counter state and update time
			/// </summary>
			/// <param name="clientID"></param>
			/// <param name="counter"></param>
			/// <param name="lastUpdated"></param>
			public VectorClockElement( string clientID, long counter, DateTime lastUpdated )
			{
				ClientID = clientID;
				Update( counter, lastUpdated );
			}

			/// <summary>
			/// Copy constructor
			/// </summary>
			/// <param name="copyFrom"></param>
			protected VectorClockElement( VectorClockElement copyFrom )
			{
				ClientID = copyFrom.ClientID;
				Counter = copyFrom.Counter;
				LastUpdated = copyFrom.LastUpdated;
				LastReceived = copyFrom.LastReceived;
			}

			/// <summary>
			/// Updates the value of the counter
			/// </summary>
			/// <param name="counter">the last value of the counter</param>
			/// <param name="lastUpdated">the UTC timestamp of the last update of the counter in the time of the client it belongs to</param>
			public void Update( long counter, DateTime lastUpdated )
			{
				if ( counter > Counter )
				{
					Counter = counter;
					LastUpdated = lastUpdated;
					LastReceived = DateTime.UtcNow;
				}
			}

			/// <summary>
			/// Increments the value of the counter by 1
			/// </summary>
			public void Increment()
			{
				Counter++;
				LastUpdated = DateTime.UtcNow;
				LastReceived = LastUpdated;
			}

			/// <summary>
			/// Creates a deep copy of this object
			/// </summary>
			/// <returns></returns>
			public VectorClockElement Clone()
			{
				return new VectorClockElement( this );
			}
		}

		#endregion Inner classes
	}
}
